package com.beiding.hotcode;

import javax.tools.*;
import java.io.File;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.*;

class MyCompiler {

    //被编译的文件单元
    private Iterable<? extends JavaFileObject> compilationUnits;

    //编译参数
    private List<String> option = new ArrayList<>();

    private Set<String> allClasspath = new HashSet<>();

    private JavaCompiler cmp = ToolProvider.getSystemJavaCompiler();

    Charset charset;

    private StandardJavaFileManager fileManager;


    //初始化参数
    MyCompiler(String cp, String encoding, String dir) {
        this.charset = Charset.forName(encoding);
        allClasspath.add(cp);
        option.add("-classpath");
        option.add(cp);
        option.add("-encoding");
        option.add(encoding);
        option.add("-d");
        option.add(dir);
        /*option.add("-Xlint:unchecked");
        option.add("-Xlint:deprecation");
        */
        resetFileManager();
    }

    private void resetFileManager() {
        fileManager = cmp.getStandardFileManager(null, null, charset);
    }

    private boolean addClasspath(Set<String> all) {

        //差集运算
        Set<String> newAll = new HashSet<>(all);
        newAll.removeAll(allClasspath);

        if (newAll.size() == 0) {
            return false;
        }

        //新加入的
        allClasspath.addAll(newAll);
        StringBuilder builder = new StringBuilder();
        for (String s : all) {
            builder.append(";").append(s);
        }
        String cp = option.get(1);
        cp += builder.toString();
        option.remove(1);
        option.add(1, cp);
        return true;
    }

    void changeJavaFiles(Collection<File> files) {
        File[] fs = new File[files.size()];
        files.toArray(fs);
        changeJavaFiles(fs);
    }

    void changeJavaFiles(File... files) {
        if (files.length == 0) {
            compilationUnits = null;
        } else {
            compilationUnits = fileManager.getJavaFileObjects(files);
        }
    }

    boolean compile() {

        DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
        JavaCompiler.CompilationTask task = cmp.getTask(null, fileManager, diagnosticCollector, this.option, null, compilationUnits);
        Boolean call = task.call();
        if (call) {
            return true;
        } else {
            return handleError(diagnosticCollector);
        }

    }

    private Set<String> getClasspath(String packageName) throws Exception {
        Set<String> ps = new HashSet<>();
        String packageDirName = packageName.replace('.', '/');
        Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
        while (resources.hasMoreElements()) {
            URL url = resources.nextElement();
            if (url.getProtocol().equals("file")) {

                //截断找出文件夹
                String path = new File(url.getFile()).getAbsolutePath();
                packageDirName = ("/" + packageDirName).replace("/", File.separator);
                path = path.substring(0, path.lastIndexOf(packageDirName));
                ps.add(path);
            } else if (url.getProtocol().equals("jar")) {
                JarURLConnection urlConnection = (JarURLConnection) url.openConnection();
                //如果文件不存在,就将该jar文件放到临时路径下

                URL jarFileURL = urlConnection.getJarFileURL();
                File file = new File(jarFileURL.getFile());
                if (file.exists()) {
                    ps.add(file.getAbsolutePath());
                } else {
                    URLConnection connection = jarFileURL.openConnection();
                    connection.connect();
                    //这里获取到输入流???
                    InputStream inputStream = connection.getInputStream();
                    File copyJarFile = FileUtils.copyJarFile(inputStream, file.getName());
                    inputStream.close();
                    ps.add(copyJarFile.getAbsolutePath());

                }
            }
        }
        return ps;
    }


    private String readClass(Diagnostic<? extends JavaFileObject> diagnostic) {
        String message = diagnostic.getMessage(Locale.ROOT);
        if (!message.startsWith("cannot access ")) {
            throw new RuntimeException("无法解析错误");
        }
        String[] split = message.split("\n");
        if (split.length < 1) {
            throw new RuntimeException("无法解析错误");
        }
        message = split[0].trim();
        return message.substring(14);
    }

    private Set<String> readClassPathFromClassname(String classname) {

        try {
            return getClasspath(Class.forName(classname).getPackage().getName());
        } catch (Exception e) {
            throw new RuntimeException("无法加载:" + classname);
        }

    }

    //处理编译过程中的错误信息
    private boolean handleError(DiagnosticCollector<JavaFileObject> diagnosticCollector) {

        try {
            Set<String> all = new HashSet<>();
            for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()) {
                if (diagnostic.getKind().equals(Diagnostic.Kind.ERROR)) {
                    switch (diagnostic.getCode()) {
                        case "compiler.err.doesnt.exist":
                            String packageName = readPackage(diagnostic);
                            System.out.println("找不到包:" + packageName);
                            all.addAll(getClasspath(packageName));
                            break;
                        case "compiler.err.cant.access":
                            String readClass = readClass(diagnostic);
                            System.out.println("无法访问类:" + readClass);
                            all.addAll(readClassPathFromClassname(readClass));
                            break;
                        default:
                            System.out.println("错误编码:  " + diagnostic.getCode());
                            System.out.println("错误信息:  \n" + diagnostic.getMessage(Locale.ROOT));
                            System.out.println("错误内容:  \n" + diagnostic);
                            break;
                    }

                }
            }

            //如果不是导包异常
            if (all.size() == 0) {
                for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()) {
                    System.out.println(diagnostic);
                }
                return false;
            }

            //如果有错误的类路径,但是无法添加进去
            if (!addClasspath(all)) {
                System.out.println("无法处理的类路径:");
                System.out.println(all);
                return false;
            }

            //重新编译前需要重置FileManager
            resetFileManager();
            System.out.println("应用解决方案:");
            System.out.println("导入类路径重新编译");
            System.out.println(all);
            return compile();

        } catch (Exception e) {
            return false;
        }

    }

    //从提示信息中找到未引入的类路径
    private String readPackage(Diagnostic<? extends JavaFileObject> javaFileObjectDiagnostic) {
        String message = javaFileObjectDiagnostic.getMessage(Locale.ROOT);
        if (!message.endsWith(" does not exist")) {
            throw new RuntimeException("无法解析错误");
        }
        message = message.substring(0, message.lastIndexOf(" does not exist"));
        if (!message.startsWith("package ")) {
            throw new RuntimeException("无法解析错误");
        }
        return message.substring(8);
    }


}
